<template>
  <div id="containerDiv"></div>
</template>

<script lang="ts">
import * as THREE from 'three';
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader"
import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
import {defineComponent} from 'vue'
const env = import.meta.env
export default defineComponent({

  name: "ThreeView2",
  data() {
    return {
      mainDomId: 'containerMain',
      domId: 'containerDiv',
      models: [{ name: '机器头', path: env.BASE_URL + 'LittlestTokyo.glb', position: [0, 0, 5], type: 'glb' }],
      ThreeApp: null,
      Scene: null,
      Renderer: null,
      Camera: null,
      Model: null,
      Lights: null,
      Controls: null,
      animationID: null,
      loadMeshList: [],
      loadDataList: [],
      // 鼠标点击
      pickEnable: true,
      pickingTexture: new THREE.WebGLRenderTarget(1, 1),
      pixelBuffer: new Uint8Array(4),
      pickedObject: null,
      pickedObjectSavedColor: 0,
      pickPosition: {},
      index: 1,
      meshList: [],
    }
  },
  mounted() {
    this.ThreeApp = document.getElementById(this.domId);
    this.run();
  },
  methods: {
    // 添加场景
    initScene: function () {
      this.Scene = new THREE.Scene()
      this.Scene.background = new THREE.Color(0x282923);
    },
    // 添加渲染器
    initRenderer: function () {
      this.Renderer = new THREE.WebGLRenderer({
        antialias: true,
        alpha: true,
        premultipliedAlpha: true,
        precision: 'highp'
      })
      this.Renderer.setPixelRatio(window.devicePixelRatio);
      this.Renderer.setSize(window.innerWidth, window.innerHeight);
      this.Renderer.setClearColor(0xeeeeee);
      this.Renderer.shadowMap.enabled = true;
      this.Renderer.physicallyCorrectLights = true;
      this.ThreeApp.appendChild(this.Renderer.domElement);
    },
    // 添加相机
    initCamera: function () {
      this.Camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000)
      this.Camera.position.set(500, 500, 500);
      this.Camera.lookAt(this.Scene.position)
    },
    // 加载模型GLTF FBX
    modelLoader: function (MODEL) {
      let _this = this;
      const loadTip = this.addLoadTip();
      this.Controls.autoRotate = false;
      let Loader = '', MTYPE = MODEL.type || 'glb';
      if ('glb,gltf'.indexOf(MTYPE) !== -1) {
        Loader = new GLTFLoader()
        // 实例化加较器draco
        const draco = new DRACOLoader();
        // 指明解压工具的地址
        draco.setDecoderPath("/bimi/draco/gltf/");
        Loader.setDRACOLoader(draco);
      } else {
        loadTip.textContent = '请使用glb,gltf格式模型';
        return;
      }
      Loader.load(
        MODEL.path,
        (geometry) => {
          loadTip.textContent = '加载完成！';
          // 移除模型
          this.Model && this.Scene.remove(this.Model);
          // 设置相机位置
          this.Model = 'fbx'.indexOf(MTYPE) !== -1 ? geometry : geometry.scene;
          // 获取包围盒中心点 将模型位置移到原点处
          const box = new THREE.Box3().setFromObject(this.Model);
          const center = box.getCenter(new THREE.Vector3());
          this.Camera.position.sub(center);
          // 设置模型缩放
          const x = (box.max.x - box.min.x)
          const y = (box.max.y - box.min.y)
          const z = (box.max.z - box.min.z)
          const maxDim = Math.max(x, y, z)
          const scale = 250 / maxDim
          this.Scene.scale.set(scale, scale, scale)
          // BufferGeometryUtils方式合并加载
          let materialMap = new Map()
          let geometryArrMap = new Map()
          // 遍历模型节点，获取相关参数设置
          let index = 1;
          this.Model.traverse(function (child) {
            // console.log(child.userData)
            child.updateMatrixWorld(true);
            if (child.isObject3D && child.geometry !== undefined && child.material !== undefined) {
              // 保存材质信息
              if (materialMap.get(child.material.uuid) === undefined) {
                materialMap.set(child.material.uuid, child.material)
              }
              // 坐标转换
              let matrixWorldGeometry = child.geometry.clone().applyMatrix4(child.matrixWorld);
              // 设置元素信息
              let count = matrixWorldGeometry.getAttribute("position").count
              let color = new THREE.BufferAttribute(new Float32Array(3 * count), 3)
              let elementID = new THREE.BufferAttribute(new Int32Array(count), 1)
              let indexList = matrixWorldGeometry.index
              for (let i = 0; i < indexList.count; i++) {
                let h = indexList.getX(i)
                elementID.setX(h, index)
                color.setXYZ(h, 1, 1, 1)
              }
              // matrixWorldGeometry.setAttribute('color', color)
              matrixWorldGeometry.setAttribute('elementID', elementID)
              // 保存数据
              _this.loadMeshList[index] = matrixWorldGeometry
              _this.loadDataList[index] = child.userData
              index++;
              // 分组保存
              if (geometryArrMap.get(child.material.uuid) !== undefined) {
                let arr = geometryArrMap.get(child.material.uuid)
                arr.push(matrixWorldGeometry)
                geometryArrMap.set(child.material.uuid, arr)
              } else {
                let arr = []
                arr.push(matrixWorldGeometry)
                geometryArrMap.set(child.material.uuid, arr)
              }
            }
          }
          );
          // 设置点击拾取自定义颜色
          let pickMaterial = new THREE.MeshBasicMaterial()
          _this.pickShader(pickMaterial)
          geometryArrMap.forEach((value, key, map) => {
            if (value !== undefined && value !== null) {
              let mergedGeometry = BufferGeometryUtils.mergeGeometries(value, false);
              let material = materialMap.get(key);
              let mesh = new THREE.Mesh(mergedGeometry, material);
              mesh.pickMaterial = pickMaterial
              mesh.origionMaterial = material
              this.Scene.add(mesh);
              this.meshList.push(mesh)
            }
          })
          // 完成加载
          loadTip.style.display = 'none';
          this.animation();
        },
        (xhr) => {
          // console.log(xhr, xhr.loaded, xhr.total)
          // 加载进度
          loadTip.textContent = '模型加载中...';
        },
        (err) => {
          console.log(err)
          loadTip.textContent = '模型加载失败！'
          console.log('模型加载失败！')
        }
      );
    },
    // 自定义材质
    pickShader(material) {
      material.onBeforeCompile = function (e) {
        e.vertexShader = `attribute int elementID;varying vec3 vpickColor;`
          + e.vertexShader.replace(/#include <fog_vertex>/, `
                #include <fog_vertex>
                float r = float((elementID >> 16) & 0xFF) / 255.0;
                float g = float((elementID >> 8) & 0xFF) / 255.0;
                float b = float((elementID >> 0) & 0xFF) / 255.0;
                vpickColor=vec3(r,g,b);
                `);
        e.fragmentShader = `varying vec3 vpickColor;`
          + e.fragmentShader.replace(/#include <dithering_fragment>/, `
                  #include <dithering_fragment>
                  gl_FragColor =vec4(vpickColor, gl_FragCoord.z);
                `);
      }
    },
    // 自定义材质
    selectShader(material) {
      material.onBeforeCompile = function (e) {
        e.vertexShader = `attribute vec3 selectedPosition;`
          + e.vertexShader.replace(/#include <fog_vertex>/, `
                #include <fog_vertex>
                gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
                `);
        e.fragmentShader = ``
          + e.fragmentShader.replace(/#include <dithering_fragment>/, `
                  #include <dithering_fragment>
                  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
                `);
      }
    },
    //加载光源
    addLight: function () {
      this.Lights = [
        { name: 'AmbientLight', obj: new THREE.AmbientLight(0xFFFFFF, 1) },
        { name: 'DirectionalLight_top', obj: new THREE.DirectionalLight(0xFFFFFF, 3), position: [0, 15, 0] },
        { name: 'DirectionalLight_bottom', obj: new THREE.DirectionalLight(0x1B1B1B, 3), position: [0, -200, 0] },
        { name: 'DirectionalLight_right1', obj: new THREE.DirectionalLight(0xFFFFFF, 1.5), position: [0, -5, 20] },
        { name: 'DirectionalLight_right2', obj: new THREE.DirectionalLight(0xFFFFFF, 1.5), position: [0, -5, -20] },
      ];
      this.Lights.map(item => {
        item.obj.name = item.name;
        item.position && item.obj.position.set(...item.position);
        item.Helper = new THREE.PointLightHelper(item.obj);
        this.Scene.add(item.obj);
      })
    },
    //添加事件
    addControls: function () {
      this.Controls = new OrbitControls(this.Camera, this.Renderer.domElement);
      // 如果使用animate方法时，将此函数删除
      //controls.addEventListener( 'change', render );
      // 使动画循环使用时阻尼或自转 意思是否有惯性
      this.Controls.enableDamping = true;
      //是否可以缩放
      this.Controls.enableZoom = true;
      //设置相机距离原点的最远距离-可以控制缩放程度
      this.Controls.minDistance = 0;
      //设置相机距离原点的最远距离
      this.Controls.maxDistance = 3000;//800
      //是否开启右键拖拽
      this.Controls.enablePan = true;
      //动态阻尼系数 就是鼠标拖拽旋转灵敏度
      this.Controls.dampingFactor = 0.5;
      //是否自动旋转
      this.Controls.autoRotate = false;
      this.Controls.autoRotateSpeed = 1;
    },
    //添加加载进度
    addLoadTip: function () {
      document.querySelector('.loadTip') && this.ThreeApp.removeChild(document.querySelector('.loadTip'));
      let loadTip = document.createElement('div');
      loadTip.className = 'loadTip'
      loadTip.style.cssText += 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);border-radius:5px;background-color:rgba(0,0,0,0.5);padding:5px 10px;color:#fff;';
      this.ThreeApp.appendChild(loadTip);
      return loadTip;
    },
    animation: function () {
      //更新控制器
      this.Renderer.render(this.Scene, this.Camera);
      this.Controls.update();
      this.animationID = requestAnimationFrame(() => this.animation());
    },
    onWindowResize: function () {
      this.Camera.aspect = window.innerWidth / window.innerHeight;
      this.Camera.updateProjectionMatrix();
      this.Renderer.setSize(window.innerWidth, window.innerHeight);
      this.Renderer.render(this.Scene, this.Camera);
    },
    run: function () {
      this.initRenderer.call(this)
      this.initScene.call(this)
      this.initCamera.call(this)
      this.addControls();
      // 添加环境光
      this.addLight()
      this.modelLoader(this.models[0]);
      window.onresize = () => this.onWindowResize();
      // 添加点击事件
      let _this = this;
      this.Renderer.domElement.addEventListener("mousedown", (event) => {
        _this.pickPosition.x = event.clientX;
        _this.pickPosition.y = event.clientY;
        _this.pick(_this.pickPosition)
      })
    },
    // 设置拾取颜色
    setPickMaterial() {
      this.meshList.forEach(item => {
        item.material = item.pickMaterial
      })
    },
    // 恢复本颜色
    resetPickMaterial() {
      this.meshList.forEach(item => {
        item.material = item.origionMaterial
      })
    },
    // 鼠标点击事件
    pick(pickPosition) {
      if (!this.pickEnable) {
        return
      }
      const { pickingTexture, pixelBuffer } = this;
      // 如果已经存在拾取的对象，将其颜色恢复
      if (this.pickedObject) {
        // this.pickedObject.material.emissive.setHex(this.pickedObjectSavedColor);
        this.pickedObject = undefined;
      }
      // 设置拾取颜色
      this.setPickMaterial()
      // 设置视野偏移来表现鼠标下的1px
      const pixelRatio = this.Renderer.getPixelRatio();
      this.Camera.setViewOffset(
        this.Renderer.getContext().drawingBufferWidth,   // 全宽
        this.Renderer.getContext().drawingBufferHeight,  // 全高
        pickPosition.x * pixelRatio | 0,             // rect x
        pickPosition.y * pixelRatio | 0,             // rect y
        1,                                          // rect width
        1,                                          // rect height
      );
      // 渲染场景
      this.Renderer.setRenderTarget(pickingTexture)
      this.Renderer.render(this.Scene, this.Camera);
      this.Renderer.setRenderTarget(null);
      // 清理视野偏移，回归正常
      this.Camera.clearViewOffset();
      // 读取像素
      this.Renderer.readRenderTargetPixels(
        pickingTexture,
        0,   // x
        0,   // y
        1,   // width
        1,   // height
        pixelBuffer);
      // 恢复本颜色
      this.resetPickMaterial()
      // 获取index
      const index =
        (pixelBuffer[0] << 16) |
        (pixelBuffer[1] << 8) |
        (pixelBuffer[2]);

      let boxObject = this.loadMeshList[index];
      let dataObject = this.loadDataList[index];
      console.log(index)

      let intersectedObject = this.meshList[0]
      if (intersectedObject && boxObject) {
        console.log(intersectedObject, dataObject)
        // 获取第一个对象，他是离鼠标最近的一个
        this.pickedObject = intersectedObject;
        // this.pickedObject.setAttribute('color', new THREE.BufferAttribute(0xFF0000, 3))

        let old = intersectedObject.material
        const material = old.clone()
        intersectedObject.geometry.setAttribute('selectedPosition', new THREE.BufferAttribute(boxObject.getAttribute('position').array, 3));
        this.selectShader(material)
        // old.dispose()
        // let material1 = new THREE.MeshBasicMaterial({ color: 0xffffff});
        // intersectedObject.material = material1

        const mesh = new THREE.Mesh(boxObject, material);
        this.Scene.add(mesh);

      }
    },
    // 释放资源
    disposeThreeJS() {
      // 清除动画帧id
      cancelAnimationFrame(this.animationID)
      //将每个模型释放
      if (this.Model !== undefined && this.Model !== null) {
        this.Model.traverse(function (v) {
          if (v.type === 'Mesh') {
            v.geometry.dispose()
            v.material.dispose()
          }
          v = null
        })
      }
      this.loadMeshList.forEach(v => {
        if (v.type === 'Mesh') {
          v.geometry.dispose()
          v.material.dispose()
        }
        v = null
      })
      if (this.Renderer !== undefined && this.Renderer !== null) {
        this.Renderer.forceContextLoss()
        this.Renderer.dispose()
        this.Renderer.domElement = null
      }
      if (this.Scene !== undefined && this.Scene !== null) {
        this.Scene.remove(this.Model)
        this.Scene.clear();
      }
      THREE.Cache.clear();
    },
    // 重置数据
    resetDate() {
      this.ThreeApp.innerHTML = ''
      this.ThreeApp = null
      this.Scene = null
      this.Renderer = null
      this.Camera = null
      this.Model = null
      this.Lights = null
      this.Controls = null
      this.animationID = null
      this.loadMeshList = []
    },
  },
  beforeDestroy() {
    this.disposeThreeJS()
  },
  destroyed() {
    this.resetDate()
  },
})
</script>
